Java中存在着四种引用类型分别为:强引用(Strong Reference),弱引用(Weak Reference),软引用(Soft Reference)和虚引用(Phantom Reference),每个引用对象内部都有一个对应的引用对象
Referent
和一个ReferenceQueue
. 这四个引用类类型的父类是Reference
。Reference 是一个抽象类,它定义了所有引用对象共同的操作方法。因为引用对象和垃圾回收操作关系密切,因此一般不需要直接实现该类。
⭐️记忆技巧:强引用(程序运行过程不会被回收) > 软引用(内存不足时被回收) > 弱引用 (垃圾回收机制执行就被回收)> 虚引用(用于跟踪对象被垃圾回收的状态)
1. 强引用(Strong Reference)
强引用通常是指将一个对象赋值给一个引用变量,这个引用变量就称为是强引用(Strong Reference),此类引用不会被GC机制回收,即使不使用该引用变量,也不会被回收,直至程序出现了OOM(Out Of Mermory)异常,所以通常强引用也是造成内存泄漏的主要原因之一,例子如下所示:
1 | public void test() { |
2. 弱引用(Weak Reference)
弱引用在垃圾回收执行时,不管JVM内存空间是否够用,该引用对象都会被回收。其引用需要使用WeakReference
类来实现,它比软引用的生存期相对更短。
其中,WeakReference中有两个构造参数,如下所示:
1 | public class WeakReference<T> extends Reference<T> { |
public WeakReference(T referent, ReferenceQueue<? super T> q)
该构造函数中的第一个参数T referent
就是弱引用对象,第二个参数ReferenceQueue<? super T> q
则是用来存储封装的待回收的Reference对象的。
具体例子如下所示:
1 | /* |
创建ref_1对象:
其中年轻代大小为5MB,随后创建的ref_1占用大小为10MB,年轻代总大小(5MB)不够存储,根据Java堆存放机制,将ref_1对象置入剩余的15MB空间中。
结果:
年轻代剩余大小:5MB
堆其他空间(含老年代)剩余大小:5MB
创建ref_2对象:
创建的ref_2对象大小为3MB,存储入年轻代Eden区中,随后将其设置为弱引用(Weak Reference)。
结果:
年轻代剩余大小:2MB
堆其他空间(含老年代)剩余大小:5MB
创建ref_3对象:
待创建的ref_3对象大小为4MB,而此时,强引用对象ref_2已解除,JVM可在剩余内存不足时进行GC回收,而因为年轻代剩余大小仅为2MB,则对其进行Minor GC回收,清理并释放弱引用(Weak Reference)的ref_2内存空间,随后放入大小为5MB的年轻代中。
此时的ref_3对象大小若为5MB,则与年轻代和堆其他空间大小形成一致,无法放入年轻代中,也无法放入堆其他空间中(因为放进任何一个都满了),随之将报OutOfMemoryError异常。
结果:
年轻代剩余大小:1MB
堆其他空间(含老年代)剩余大小:5MB
3. 软引用(Soft Reference)
软引用与弱引用十分相似,但总有区别。弱引用(Weak Reference)在遇到系统进行垃圾回收时,不管其剩余空间是否足够用,都将被回收,而软引用(Soft Reference)当遇到系统进行垃圾回收时,剩余内存空间足够时,不会被回收,只有在内存不足时,才会被回收。也就是说软引用(Soft Reference)在当没有强引用(Strong Reference)指向它时,会在内存中停留一段时间。测试例子与上大部分相同,读者可自行修改后尝试。
1 | // Soft Reference Usage |
软引用有以下特征:
- 软引用使用
get()
方法取得对象的强引用从而访问目标对象。 - 软引用所指向的对象按照 JVM 的使用情况(Heap 内存是否临近阈值)来决定是否回收。
- 软引用可以避免 Heap 内存不足所导致的异常。
当垃圾回收器决定对其回收时,会先清空它的 SoftReference
,也就是说 SoftReference
的get()
方法将会返回 null,然后再调用对象的finalize()
方法,并在下一轮 GC 中对其真正进行回收。
4. 虚引用(Phantom Reference)
虚引用(Phantom Reference)是所有”弱引用”中最弱的引用类型。不同于软引用和弱引用,虚引用无法通过 get() 方法来取得目标对象的强引用从而使用目标对象,观察源码可以发现 get() 被重写为永远返回 null。
1 | /** |
虚引用需要使用PhantomReference
类来实现,并且它不能单独使用,必须和引用队列ReferenceQueue
一起使用,使用例子如下所示:
1 | public class RefTest { |
对于引用回收方面,虚引用(Phantom Reference)类似强引用(Strong Reference)不会根据内存情况自动对对象空间进行回收处理,此时则需要我们手动对其处理以防Heap空间不足异常。
虚引用有以下特征:
- 虚引用永远无法使用
get()
方法取得对象的强引用从而访问目标对象。 - 虚引用所指向的对象在被系统内存回收前,虚引用自身会被放入
ReferenceQueue
对象中从而跟踪对象垃圾回收。 - 虚引用不会根据内存情况自动回收目标对象。
另外值得注意的是,其实 SoftReference
, WeakReference
以及 PhantomReference
的构造函数都可以接收一个 ReferenceQueue
对象。当 SoftReference
以及WeakReference
被清空的同时,也就是 Java 垃圾回收器准备对它们所指向的对象进行回收时,调用对象的 finalize() 方法之前,它们自身会被加入到这个 ReferenceQueue 对象
中,此时可以通过 ReferenceQueue
的 poll()
方法取到它们。而 PhantomReference 只有当 Java 垃圾回收器对其所指向的对象真正进行回收时,会将其加入到这个 ReferenceQueue 对象
中,这样就可以追综对象的销毁情况。
5. 总结
引用类型 | 取得目标对象方式 | 垃圾回收条件 | 是否可能内存泄漏 |
---|---|---|---|
强引用 | 直接调用 | 不回收 | 可能 |
软引用 | 通过 get() 方法 | 视内存情况回收 | 不可能 |
弱引用 | 通过 get() 方法 | 永远回收 | 不可能 |
虚引用 | 无法取得 | 不回收 | 可能 |
注意:如果想使用这些相对强引用来说较弱的引用来进行对象操作的时候,就必须保证没有强引用指向被操作对象。否则将会被视为强引用指向,不会具有任何的弱引用的特性(可参考本文中弱引用(Weak Reference)示例代码中ref_2的置null操作)。